﻿using System;
using System.Web;
using System.IO;
using System.Collections.Generic;
using System.Xml.Linq;
using System.Text;
using System.Web.Configuration;

namespace Doo.HttpHandlers
{
    /// <summary>
    /// 输出控件地图以及依赖
    /// 主要查找
    /// </summary>
    public class ControlMapHandler : IHttpHandler
    {

        string CFG(string name)
        {
            return System.Web.Configuration.WebConfigurationManager.AppSettings[name];
        }

        bool debug { get; set; }

        /// <summary>
        /// 命名空间
        /// </summary>
        string ctlns { get; set; }

        /// <summary>
        /// 控件定义
        /// </summary>
        class ControlDef
        {
            /// <summary>
            /// 类型名
            /// </summary>
            public string TypeName { get; set; }
            /// <summary>
            /// 对应资源编号
            /// </summary>
            public int ResIndex { get; set; }
            /// <summary>
            /// 依赖资源集
            /// </summary>
            public string[] DependResIndex { get; set; }
        }

        void ScanDir(string dir, List<ControlDef> defs, ResourceM rm, ResourceDir basedir, Dictionary<string, int> serviceIndex, int rootlen)
        {
            int fulli = dir.Length;
            string[] jsfile = Directory.GetFiles(dir, "*.js");
            #region 扫描文件
            foreach (string js in jsfile)
            {
                string typename = js.Substring(rootlen, js.Length - 3 - rootlen);
                bool isdebug = typename.EndsWith(".debug");
                string tfn = js.Remove(js.Length - (isdebug ? 9 : 3));
                if (isdebug)
                {
                    typename = typename.Remove(typename.Length - 6).Replace('\\', '.');
                }

                ControlDef cdef = new ControlDef { TypeName = typename };

                bool hashtmres = false;//是否有htm资源

                #region 扫描附加文件
                List<string> indexs = new List<string>();

                string htmfn = tfn + ".htm";
                if (File.Exists(htmfn))
                {
                    //存在htm文件，加入资源中
                    if (debug) indexs.Add(rm.AddResource(basedir, htmfn.Substring(fulli), true).ToString());
                    hashtmres = true;
                }

                string resfn = tfn + ".res";
                if (File.Exists(resfn))
                {
                    //存在资源文件，查找资源
                    /* 格式
                     * <config type="">
                     * <services>
                     * <item>Doo.Services.A</item>
                     * </services>
                     * <files>
                     * </files>
                     * </config>
                     */
                    var xdroot = XDocument.Load(resfn).Root;
                    var typeA = xdroot.Attribute("type");
                    if (typeA != null) cdef.TypeName = typeA.Value;

                    #region Web服务依赖

                    var services = xdroot.Element("services");
                    if (services != null && services.HasElements)
                    {
                        var ss = services.Elements();
                        foreach (var sse in ss)
                        {
                            indexs.Add(serviceIndex[sse.Value].ToString());
                        }
                    }

                    #endregion

                    #region 额外要加载的文件，如脚本库
                    var files = xdroot.Element("files");
                    if (files != null && files.HasElements)
                    {
                        var rfs = xdroot.Elements();
                        foreach (var rfe in rfs)
                        {
                            string rf = rfe.Value;
                            if (rf[0] == '/' || rf[0] == '~')
                            {
                                //根目录
                                indexs.Add(rm.AddResource(rf, true).ToString());
                            }
                            else
                            {
                                indexs.Add(rm.AddResource(basedir, rf, true).ToString());
                            }
                        }
                    }
                    #endregion

                }

                #endregion

                int cindex = 0;
                if (isdebug && (hashtmres || debug))
                {
                    //转换 调试版本至正式版本
                    string source = File.ReadAllText(js);
                    if (!debug)
                    {
                        source = new Microsoft.Ajax.Utilities.Minifier().MinifyJavaScript(source);
                    };
                    if (hashtmres)
                    {
                        string fulltype = typename;
                        if (!string.IsNullOrEmpty(ctlns))
                        {
                            fulltype = ctlns + "." + typename;
                        }

                        string cssres = tfn + ".css";
                        bool hascssres = File.Exists(cssres);

                        source = CreateCJSContent(fulltype, source, File.ReadAllText(htmfn), hascssres ? File.ReadAllText(cssres) : null);
                        source += string.Format("\r\n{0}.prototype.staticcontent=\"{1}\";", fulltype, File.ReadAllText(htmfn)
                            .Replace("\"", "\\\"")
                            .Replace("\r", "\\\r")
                            );
                    }
                    string newjs = js.Remove(js.Length - 9) + ".cjs";
                    File.WriteAllText(newjs, source);
                    cindex = rm.AddResource(basedir, newjs.Substring(fulli), true);
                }
                else
                {
                    cindex = rm.AddResource(basedir, js.Substring(fulli), true);
                }

                cdef.ResIndex = cindex;

                defs.Add(cdef);


                if (indexs.Count > 0) cdef.DependResIndex = indexs.ToArray();
            }
            #endregion

            #region 扫描子目录

            string[] dirs = Directory.GetDirectories(dir);
            foreach (var dirname in dirs)
            {
                ResourceDir rd = rm.FindDir(dirname.Substring(fulli), basedir);

                ScanDir(dirname + '\\', defs, rm, rd, serviceIndex, rootlen);
            }

            #endregion
        }

        /// <summary>
        /// 创建CJS内容
        /// </summary>
        /// <param name="fulltype"></param>
        /// <param name="js"></param>
        /// <param name="htm"></param>
        /// <param name="css"></param>
        /// <returns></returns>
        public static string CreateCJSContent(string fulltype, string js, string htm, string css)
        {
            js += string.Format("\r\n{0}.prototype.staticcontent=\"{1}\";", fulltype, htm
                            .Replace("\"", "\\\"")
                            .Replace("\r", "\\\r")
                            );
            if (!string.IsNullOrEmpty(css)) js += string.Format("\r\n{0}.prototype.csscontent=\"{1}\";", fulltype, css
                             .Replace("\"", "\\\"")
                             .Replace("\r", "\\\r")
                             );
            return js;
        }

        static string cachedOutput;
        static FileSystemWatcher watcher;

        /// <summary>
        /// 扫描目录并生成依赖集
        /// 扫描以下文件：*.js以及同名的*.res（如果有的话）
        /// </summary>
        /// <returns></returns>
        public string ScanAndCreate()
        {
            /*
             * web.config将负责定义Doo控件目录及Web服务定义
             * 控件目录下的doo.config将定义控件的命名空间及资源合并选项
             * 
             * 处理doo.config文件
             * 该文件指定了此目录下js的命名空间和资源合并及重命名
             * 范例：
             * <doo>
             *  <control namespace="" />
             *  <compoiseres>
             *      <group type="css/js/text/xml" name="" savepath="">
             *          通过指定type来实现不同的合并和载入方式，未指定时将默认按照savepath的后缀名来指定
             *          如xml的合并方式就很特殊，将使用<XmlCollection><XML>...</XML><XML>...</XML></XmlCollection>的方式来合并。【暂不实现】
             *        <res path="" name="" />
             *        <res path="" name="" />
             *      </group>
             *      <group savepath="">
             *      </group>
             *  </compoiseres>
             * </doo>
             * 
             * 每个控件可以有同名的.res文件，该文件指定了此控件的Type以及依赖资源【如依赖控件】用以并行载入。
             * 
             * 
             */

            if (cachedOutput != null) return cachedOutput;

            Configuation.CustomControlSection config = System.Web.Configuration.WebConfigurationManager.GetSection("customcontrol") as Configuation.CustomControlSection;
            debug = config.Controls.Debug;
            ctlns = config.Controls.NameSpace;
            System.Text.StringBuilder sb = new System.Text.StringBuilder();
            ResourceM rm = new ResourceM();
            ResourceDir basedir = null;
            Dictionary<string, int> serviceIndex = new Dictionary<string, int>();

            //1.输出webservices
            //格式：Doo.ControlLoader.initDependServices([[name,path,type]]);
            if (config.WebServices.Count > 0)
            {
                sb.Append("Doo.ControlLoader.initDependServices({");
                if (!string.IsNullOrEmpty(config.WebServices.BasePath)) basedir = rm.FindDir(config.WebServices.BasePath, null);
                foreach (Configuation.WebServiceElement ws in config.WebServices)
                {
                    int i = rm.AddResource(basedir, ws.Path + "/js", false);
                    sb.AppendFormat("\"{0}\":[{1},\"{2}\"],", ws.Name, i, ws.Type);
                    serviceIndex.Add(ws.Name, i);
                };
                sb[sb.Length - 1] = '}';
                //附加基准名字空间
                sb.AppendFormat(",\"{0}\");", config.WebServices.NameSpace);
            }

            string path = config.Controls.BasePath;
            if (!path.EndsWith("/")) path = path + "/";
            //string rmdir = (config.Controls.Domain ?? "") + path;
            basedir = rm.FindDir(path, null);
            string ns = config.Controls.NameSpace;
            string fullpath = HttpContext.Current.Server.MapPath(path);

            if (watcher == null)
            {
                watcher = new FileSystemWatcher(fullpath);
                watcher.IncludeSubdirectories = true;
                watcher.Changed += new FileSystemEventHandler(watcher_Changed);
                watcher.Renamed += new RenamedEventHandler(watcher_Renamed);
                watcher.Created += new FileSystemEventHandler(watcher_Changed);
                watcher.Deleted += new FileSystemEventHandler(watcher_Changed);
                watcher.EnableRaisingEvents = true;
            }

            int fulli = fullpath.Length;
            //生成控件定义和依赖列表
            List<ControlDef> ControlDefs = new List<ControlDef>();

            ScanDir(fullpath, ControlDefs, rm, basedir, serviceIndex, fulli);

            //构建控件定义字符串
            sb.Append("Doo.ControlLoader.initControlDefs({");
            string domain = config.Controls.Domain;
            //if (!string.IsNullOrEmpty(domain)) sb.AppendFormat("\"http:/\":{{\"{0}\":{{", domain);

            foreach (var cd in ControlDefs)
            {
                sb.AppendFormat("\"{0}\":[{1}{2}],", cd.TypeName, cd.ResIndex, cd.DependResIndex == null ? "" : (",[" + string.Join(",", cd.DependResIndex)) + "]");
            }
            sb[sb.Length - 1] = '}';

            //if (!string.IsNullOrEmpty(domain)) sb.Append("}}");
            string tagPrefix = config.Controls.TagPreFix;
            if (string.IsNullOrEmpty(tagPrefix)) tagPrefix = "uc";
            sb.AppendFormat(",\"{0}\",\"{1}\");", ns, tagPrefix);

            cachedOutput = string.Format("Doo.ControlLoader.initResources({0});", rm.ToJson(config.Controls.Domain)) + sb.ToString();
            Extension.VersionCache.UpdateVersion(VersionKey);
            return cachedOutput + ";";//通过多一个分号标识这是第一次输出还是缓存的输出
        }

        void watcher_Renamed(object sender, RenamedEventArgs e)
        {
            try
            {
                cachedOutput = null;
                Extension.VersionCache.RemoveVersion(VersionKey);
            }
            catch
            {

            }
        }

        void watcher_Changed(object sender, FileSystemEventArgs e)
        {
            try
            {
                cachedOutput = null;
                Extension.VersionCache.RemoveVersion(VersionKey);
            }
            catch
            {

            }
        }

        /// <summary>
        /// 您将需要在您网站的 web.config 文件中配置此处理程序，
        /// 并向 IIS 注册此处理程序，然后才能进行使用。有关详细信息，
        /// 请参见下面的链接: http://go.microsoft.com/?linkid=8101007
        /// </summary>
        #region IHttpHandler Members

        public bool IsReusable
        {
            // 如果无法为其他请求重用托管处理程序，则返回 false。
            // 如果按请求保留某些状态信息，则通常这将为 false。
            get { return true; }
        }

        const string VersionKey = "Doo.ControlMap";

        public void ProcessRequest(HttpContext context)
        {
            Extension.VersionCache.CompareRequestVersion(VersionKey, true);

            context.Response.Write(ScanAndCreate());
            Extension.VersionCache.SetResponseVersion(VersionKey);

        }

        #endregion


        class ResourceDir
        {
            public string FullPath;

            public Dictionary<string, ResourceDir> Dir = new System.Collections.Generic.Dictionary<string, ResourceDir>();
            /// <summary>
            /// 文件名--资源序号+最后修改时间[如果文件不存在则为-1]
            /// </summary>
            public Dictionary<string, int[]> Files = new System.Collections.Generic.Dictionary<string, int[]>();

        }

        class ResourceM
        {
            public ResourceDir Root = new ResourceDir() { FullPath = "/" };

            Dictionary<string, int> CachedRes = new Dictionary<string, int>();

            /// <summary>
            /// 返回对应的路径
            /// </summary>
            /// <param name="path">路径</param>
            /// <param name="basedir">基准路径，为空则为root</param>
            /// <returns></returns>
            public ResourceDir FindDir(string path, ResourceDir basedir)
            {
                string[] paths = path.Split('/');
                if (basedir == null) basedir = Root;
                if (paths[0] == "~") paths[0] = string.Empty;//忽略掉~/开头的
                foreach (var p in paths)
                {
                    if (p == string.Empty) continue;
                    if (basedir.Dir.ContainsKey(p))
                    {
                        basedir = basedir.Dir[p];
                    }
                    else
                    {
                        ResourceDir newdir = new ResourceDir();
                        basedir.Dir.Add(p, newdir);
                        newdir.FullPath = basedir.FullPath + '/' + p;
                        basedir = newdir;
                    }
                }
                return basedir;
            }

            int index = 0;

            /// <summary>
            /// 添加资源，返回序号。
            /// </summary>
            /// <param name="path"></param>
            /// <returns></returns>
            public int AddResource(ResourceDir basedir, string path, bool needVersion)
            {
                if (basedir == null) basedir = Root;
                string fullpath = basedir.FullPath + '/' + path;
                if (CachedRes.ContainsKey(fullpath)) return CachedRes[fullpath];

                int i = path.LastIndexOf('/');
                if (i >= 0)
                {
                    basedir = FindDir(path.Substring(0, i), basedir);
                    path = path.Substring(i + 1);
                }

                index++;
                int v = 0;
                if (needVersion)
                {
                    string realpath = HttpContext.Current.Server.MapPath(fullpath);
                    if (File.Exists(realpath))
                    {
                        v = (int)(File.GetLastWriteTime(realpath).Ticks);
                    }
                }
                basedir.Files.Add(path, new int[] { index, v });
                CachedRes.Add(fullpath, index);

                return index;
            }

            public int AddResource(string path, bool needVersion)
            {
                int i = path[0] == '/' ? 1 : (path[0] == '~' && path[1] == '/') ? 2 : 0;
                if (i > 0) path = path.Substring(i);
                return AddResource(null, path, needVersion);
            }

            public int AddResource(string fullpath)
            {
                return AddResource(fullpath, true);
            }

            public int AddResource(ResourceDir dir, string path)
            {
                return AddResource(dir, path, true);
            }

            /// <summary>
            /// 序列化
            /// </summary>
            /// <param name="dir">肯定有值的目录</param>
            /// <param name="sb"></param>
            bool jsonSerDir(ResourceDir dir, System.Text.StringBuilder sb)
            {
                sb.Append('{');

                int l = sb.Length;//用于判断到底有没有资源被添加

                #region 处理下级目录

                foreach (var d in dir.Dir)
                {
                    int ol = sb.Length;
                    sb.AppendFormat("\"{0}\":", d.Key);
                    if (!jsonSerDir(d.Value, sb)) sb.Length = ol;
                    else sb.Append(",");
                }

                #endregion

                #region 处理文件

                foreach (var d in dir.Files)
                {
                    sb.AppendFormat("\"{0}\":[{1},{2}],", d.Key, d.Value[0], d.Value[1]);//[资源序号，版本号]版本号是最后修改时间
                }

                #endregion

                if (sb.Length == l)
                {
                    sb.Length--;
                    return false;
                }
                else
                {
                    sb[sb.Length - 1] = '}';
                    return true;
                }
            }

            /// <summary>
            /// 将资源转换成json
            /// </summary>
            /// <returns></returns>
            public string ToJson()
            {
                System.Text.StringBuilder sb = new System.Text.StringBuilder();
                jsonSerDir(Root, sb);
                return sb.ToString();
            }

            /// <summary>
            /// 将资源转换成json
            /// </summary>
            /// <returns></returns>
            public string ToJson(string pre)
            {
                if (string.IsNullOrEmpty(pre)) return ToJson();

                System.Text.StringBuilder sb = new System.Text.StringBuilder();
                string[] ps = pre.Split(new string[] { "//" }, StringSplitOptions.None);
                foreach (var p in ps)
                {
                    sb.Append("{\"" + p + "\":");
                }
                jsonSerDir(Root, sb);
                sb.Append('}', ps.Length);
                return sb.ToString();
            }
        }

    }
}
